/* 
 * Autores:
 *      Jonathan Lopez  11106
 *      Jorge Martínez  11237
 * 
 * Archivo: Viaje.java
 * Última Revision: $Rev: 69 $
 *      Fecha: $Date: 2011-09-19 06:58:38 +0000 (Mon, 19 Sep 2011) $
 */ 

package Manejo.Transporte;

import Manejo.Transporte.Registros.*;
import Archivador.iArchivable;

import java.util.Date;

/**
 * Indica la trayectoria en la que se encuentra el barco, así como el puerto
 * en el que está atracado.
 * 
 * @author Jonathan López
 * @author Jorge Martinez
 */
public class Viaje implements iArchivable
{
    private int puertoActual;
    private final ID idRuta;
    private Date fechaInicio;
    
    private RegRutas registroRutas;
    private RegBarcos registroBarcos;
    private RegPuertos registroPuertos;
    private RegContenedores registroContenedores;
    
    private final Registros registros;

    /**
     * Constructor del Viaje
     * @param idRuta ID de la Ruta a la cual se apegará este Viaje
     * @param registroRutas
     * @param registroPuertos
     */
    public Viaje(ID idRuta, Date fechaInicio, Registros registros )
    {
        this.idRuta = idRuta;
        this.registroRutas = registros.obtenerRegistroRutas();
        this.registroBarcos = registros.obtenerRegistroBarcos();
        this.registroPuertos = registros.obtenerRegistroPuertos();
        this.registroContenedores = registros.obtenerRegistroContenedores();
        this.registros = registros;
        
        this.fechaInicio = fechaInicio;
        
        
        puertoActual = 0;
    }

    /**
     * Regresa el ID del Puerto en el que se encuentra actualmente el Viaje.
     * @return ID del Puerto, o idNulo si no se ha empezado la Ruta.
     */
    public ID obtenerPuertoActual()
    {
        // Obtener la Ruta
        Ruta rutaActual = registroRutas.obtenerRuta(idRuta);
        if( rutaActual == null )
            return ID.idNulo;
        
        // Obtener el Puerto
        ID[] puertosRuta = rutaActual.obtenerPuertos();
        if( puertosRuta.length == 0 )
            return ID.idNulo;
        
        if( puertoActual == puertosRuta.length )
            return puertosRuta[puertoActual-1];
        else
            return puertosRuta[puertoActual];
    }
    
    /**
     * 
     * @return Regresa el índice, dentro de la Ruta, del Puerto en el que se encuentra
     * el Viaje
     */
    public int obtenerIndicePuertoActual()
    {
        return puertoActual;
    }
    
    /**
     * Regresa el ID de la Ruta a la cual se apega el Viaje.
     * @return
     */
    public ID obtenerRuta()
    {
        return idRuta;
    }

    /**
     * Avanza el Barco dado la cantidad de Puertos que se puedan hasta la 
     * fecha dada.
     * @param fecha Fecha a la que se moverá el Barco. La fecha tiene que ser
     * posterior a la dada como fecha de inicio.
     * @param idBarco ID del Barco a mover. El Barco debe de estar usando
     * este Viaje para poder moverse.
     * @return Regresa true si se pudo realizar la operación, o false si 
     * hubo algún problema.
     */
    public boolean avanzarPuerto( Date fecha, ID idBarco ){
        // Revisar que el Viaje no haya sido completado
        if( viajeCompletado() )
            return true;
        
        // Revisar que la fecha sea posterior a la de inicio.
        if( fecha.before(fechaInicio) )
            return false;
        Long tiempoViaje = fecha.getTime() - fechaInicio.getTime();
        
        // Revisar que la ruta sea valida
        Ruta rutaActual = registroRutas.obtenerRuta(idRuta);
        if( rutaActual == null )
            return false;
        
        // Obtener y validar el Barco
        Barco barcoActual = registroBarcos.obtenerBarco(idBarco);
        if( barcoActual == null )
            return false;
        if( barcoActual.obtenerViaje() != this )
            return false;
        
        // Obtener los puertos de la ruta
        ID[] idPuertos = rutaActual.obtenerPuertos();
        Puerto[] puertos = new Puerto[idPuertos.length];
        for (int n = 0; n != idPuertos.length; n++) {
            puertos[n] = registroPuertos.obtenerPuerto( idPuertos[n] );
            if( puertos[n] == null )
                return false;
        }
        
        // Obtener la cantidad de Puertos que podría avanzar el Barco hasta la
        // fecha
        int puertosAvanzados = 0;
        Date[] tiemposDeViaje = rutaActual.obtenerTiempos();
        long tiempoRestante = tiempoViaje;
        for( Date tiempoPuerto: tiemposDeViaje ){
            if( tiempoRestante >= tiempoPuerto.getTime() ){
                ++puertosAvanzados;
                tiempoRestante -= tiempoPuerto.getTime();
            } else
                break;
        }
        
        // Avanzar los Puertos posibles (el primer Puerto de la Ruta
        // siempre tiene tiempo 0, es por ello que no se toma en cuenta al avanzar)
        Date estampaTiempo = (Date)fechaInicio.clone();
        for (int n = 0; n != puertosAvanzados; n++) {
            // Estampa de tiempo para el puerto actual
            estampaTiempo = new Date( estampaTiempo.getTime() +
                    tiemposDeViaje[n].getTime() );
            
            if( n >= puertoActual ){
                // Se mueve el Barco al Puerto
                
                // Llegada
                puertos[n].atracarBarco(idBarco);
                
                // Agregar evento de llegada al puerto a todos los 
                // Contenedores del Barco
                for( ID contenedor: barcoActual.obtenerContenedores() ){
                    Contenedor contenedorActual = 
                            registroContenedores.obtenerContenedor(contenedor);
                    if( contenedorActual != null )
                        contenedorActual.agregarEvento( idPuertos[n],
                                estampaTiempo );
                }
                
                // Salida
                puertos[n].zarparBarco(idBarco);
                ++puertoActual;
            }
        }
        return true;
    }

    /**
     * Estima la fecha de llegada al Puerto dado.
     * @param idPuerto ID del Puerto a estimar
     * @return regresa la fecha estimada de llegada al Puerto dado. Si el Puerto
     * no existe o falla la operación, entonces se regresa null.
     */
    public Date estimarLlegadaPuerto( ID idPuerto ){
        // Revisar que la ruta sea valida
        Ruta rutaActual = registroRutas.obtenerRuta(idRuta);
        if( rutaActual == null )
            return null;
        
        // Obtener los puertos de la ruta
        boolean puertoEncontrado = false;
        int numPuerto = 0;
        ID[] idPuertos = rutaActual.obtenerPuertos();
        for (int n = 0; n != idPuertos.length; n++) {
            if( idPuertos[n].equals(idPuerto) ){
                puertoEncontrado = true;
                numPuerto = n;
            }
        }
        
        if( !puertoEncontrado )
            return null;
        
        // Estimar el tiempo de viaje
        Date tiempoViaje = new Date( fechaInicio.getTime() );
        Date[] tiempos = rutaActual.obtenerTiempos();
        for (int n = 0; n <= numPuerto; n++)
            tiempoViaje = new Date( tiempoViaje.getTime() + tiempos[n].getTime() );
        
        return tiempoViaje;
    }
    
    /**
     * @return Regresa true si el viaje ha sido completado, es decir que el 
     * Barco se encuentra en el último Puerto; o false en caso contrario.
     */
    public boolean viajeCompletado(){
        Ruta rutaViaje = registroRutas.obtenerRuta(idRuta);
        int cantidadPuertos = rutaViaje.obtenerPuertos().length;
        
        if( puertoActual == cantidadPuertos )
            return true;
        else
            return false;
    }

    /**
     * Regresa una representación String del Viaje con puerto actual, ID de
     * la Ruta y la fecha de inicio.
     * @return
     */
    @Override
    public String toString()
    {
        return "Viaje{" + "fechaInicio=" + fechaInicio +
                ", " + registroRutas.obtenerRuta(idRuta).toString() + '}';
    }
    
    
    
    /**
     * Regresa un línea de texto para ser guardada en un medio persistente.
     * @return String
     * @see iArchivable#guardar() 
     */
    @Override
    public String guardar(){
        // Orden: siguientePuerto,  idRuta, fechaInicio
        return "[Viaje" + separador + puertoActual + separador + 
                idRuta.guardar() + separador + fechaInicio.getTime() + "]";
    }
    
    /**
     * Genera un nuevo Viaje a partir de una línea de texto creada a través
     * del método guardar()
     * @param linea
     * @param registroRutas
     * @param registroPuertos
     * @return nuevo Viaje a partir del String, o null si falla
     */
    public static Viaje parseViaje( String linea, Registros registros ){
        /* Validar la linea */
        if( !Archivador.Util.validarLinea(linea) )
            return null;
        
        /* Verificar que la línea sea de un viaje */
        int totalPartes = 4;
        String srViaje = linea.substring( 1, linea.length()-1  );
        String[] partes = srViaje.split( separador );
        if( partes.length != totalPartes )
            return null;
        
        /* Verificar que la primer parte sea de un viaje */
        if( !partes[0].equals("Viaje") )
            return null;
        
        /* Parsear las demás partes */
        int _siguientePuerto = Integer.parseInt( partes[1] );
        ID _idRuta = ID.parseID( partes[2] );
        Date _fechaInicio = new Date( Long.parseLong(partes[3]) );
        
        // Validar IDs
        if( _idRuta.equals( ID.idNulo ) )
            return null;
        
        // Crear nuevo viaje
        Viaje nuevoViaje = new Viaje( _idRuta, _fechaInicio,
                registros );
        nuevoViaje.puertoActual = _siguientePuerto;
        return nuevoViaje;
    }
    
    /**
     * Punto de entrada para la prueba de un Viaje.
     * @param args
     */
//    public static void main( String[] args ){
//        ID rutaID = GeneradorID.nuevoID();
//        int fallos = 0;
//        
//        // Generar y probar varios viajes
//        int pruebas = 10;
//        Viaje[] viajes = new Viaje[pruebas];
//        for (int n = 0; n != pruebas; n++) {
//            viajes[n] = new Viaje( rutaID, new Date(), null, null, null, null );
//            String linea = viajes[n].guardar();
//            System.out.println(linea);
//            Viaje nuevoViaje = Viaje.parseViaje(linea, null, null, null, null );
//            
//            boolean iguales = viajes[n].toString().equals( nuevoViaje.toString() );
//            if( !iguales )
//                ++fallos;
//            System.out.println(linea + " && " + iguales );
//            System.out.println("\t" + viajes[n].toString() );
//            System.out.println("\t" + nuevoViaje.toString() );
//        }
//        System.out.println("FALLOS: " + fallos);
//    }
}
